package com.guojing.jl.nio.selector;

import com.guojing.jl.nio.util.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * @author: guojing
 * @create: 2023/12/7 12:46
 */
@Slf4j
public class SelectorServer {


    /**
     * selector管理多个channel
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        //1.创建selector，一个selector管理多个channel
        Selector selector = Selector.open();

        //2.启动serverchannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(9098));

        //3.简历selector和channel的关系
        SelectionKey serverChannelSelKey = serverSocketChannel.register(selector, 0, null);
        log.info("serverChannelSelKey={}",serverChannelSelKey);
        //只关注accept事件
        serverChannelSelKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            //4.select 方法。没有事件发生时阻塞，不用空执行
            selector.select();
            //5.处理事件: 所有发生的事件,事件要么处理要么取消，不能什么也不做
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();

                //接下来要处理，必须删除key，否则事件还会继续被处理，可能导致空指针
                iterator.remove();

                log.info("key={},serverChannelSelKey={}",key,serverChannelSelKey);

                if(key.isAcceptable()){
                    //channel等于 serverSocketChannel
                    ServerSocketChannel currServerSocketChannel = (ServerSocketChannel) key.channel();
                    //accept表示处理事件
                    SocketChannel socketChannel = currServerSocketChannel.accept();
                    //非阻塞模式
                    socketChannel.configureBlocking(false);
                    SelectionKey socketChannelSelectionKey = socketChannel.register(selector, 0, null);
                    socketChannelSelectionKey.interestOps(SelectionKey.OP_READ);
                    log.info("socketChannel={},socketChannelSelectionKey={}", socketChannel, socketChannelSelectionKey);


                }else if(key.isReadable()){

                    try{
                        SocketChannel currSocketChannel = (SocketChannel) key.channel();

                        //可能出现消息边界问题： 当写入的内容大于10的时候需要扩容，否则可能消息丢失
                        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                        int read = currSocketChannel.read(byteBuffer);
                        if(read == -1){
                            //如果是客户端正常断开，也会产生一个读事件，并且返回-1
                            key.cancel();
                        }else{
                            byteBuffer.flip();
//                            ByteBufferUtil.debugRead(byteBuffer);
                            System.out.println(Charset.defaultCharset().decode(byteBuffer));
                            log.info("currSocketChannel={},key={}",currSocketChannel,key);

                            //1.支持消息边界
                            split(byteBuffer);

                        }

                    }catch (Exception e){
                        //客户端强制断开之后会出现异常： 客户单关闭之后出产生一个read事件，所以需要处理这个read事件,此处取消事件
                        key.cancel();
                    }


                }
            }

        }

    }


    public static void split(ByteBuffer source) {

        for (int i = 0; i < source.limit(); i++) {
            // 找到一条完整消息： get(i)不会造成position后移
            if (source.get(i) == '\n') {
                int length = i + 1 - source.position();
                // 把这条完整消息存入新的 ByteBuffer
                ByteBuffer target = ByteBuffer.allocate(length);
                // 从 source 读，向 target 写
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                ByteBufferUtil.debugAll(target);
            }
        }

        //把未读取到的内容依次向前移动
        source.compact();
    }
}
